use std::char::{decode_utf16, REPLACEMENT_CHARACTER};

use pom::parser::*;
fn string<'a>() -> Parser<'a, char, String> {
    let char_string = is_a(|c: char| {
        c.is_alphanumeric() || c == '_' || c == '-' || c == '.' || c == ' ' || c == '\''
    })
    .repeat(1..)
    .map(|v| String::from_iter(v));
    let utf16_char = tag("\\u")
        * is_a(|c: char| c.is_digit(16))
            .repeat(4)
            .map(String::from_iter)
            .convert(|digits| u16::from_str_radix(&digits, 16));
    let utf16_string = utf16_char.repeat(1..).map(|chars| {
        decode_utf16(chars)
            .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
            .collect::<String>()
    });
    let string = (char_string | utf16_string).repeat(0..);
    string.map(|strings| strings.concat())
}
fn linebreak<'a>() -> Parser<'a, char, ()> {
    sym('\r').opt() * sym('\n').discard()
}
fn comment<'a>() -> Parser<'a, char, String> {
    let exp = space() * tag("--") * string() - linebreak();
    exp.map(|v| v)
}
fn space<'a>() -> Parser<'a, char, ()> {
    one_of(" \t\r\n").repeat(0..).discard()
}
fn divi_type<'a>() -> Parser<'a, char, String> {
    let dt = tag("/**@divi_type:")
        * space()
        * (tag("comp_acco")
            | tag("indi_loan")
            | tag("indi_save")
            | tag("comp_biz_fee")
            | tag("indi_biz_fee"))
        - space()
        - tag("*/")
        - space();
    dt.map(|v| v.to_owned())
}
fn char_string<'a>() -> Parser<'a, char, String> {
    let exp = is_a(|c: char| c.is_alphanumeric() || c == '_' || c == ' ' || c == '.').repeat(1..);
    exp.map(|v| String::from_iter(v))
}
fn names_str<'a>() -> Parser<'a, char, String> {
    let exp = is_a(|c: char| c.is_alphanumeric() || c == '_' || c == ' ' || c == '.' || c == ',').repeat(1..);
    exp.map(|v| String::from_iter(v))
}
fn name<'a>() -> Parser<'a, char, String> {
    tag("/**@name:") * space() * char_string() - space() - tag("*/") - space()
}
fn is_preproc<'a>() -> Parser<'a, char, String> {
    tag("/**@is_preproc:") * space() * char_string() - space() - tag("*/") - space()
}
fn ref_columns<'a>() -> Parser<'a, char, Vec<String>> {
    let names = tag("/**@ref_columns:") * space() * names_str() - space() - tag("*/") - space();
    names.map(|v| {
        let cols: Vec<String> = v.split(',').into_iter().map(|v| v.to_owned()).collect();
        cols
    })
}
fn col_names<'a>() -> Parser<'a, char, Vec<String>> {
    let elems = list(char_string(), sym(',') * space());
    let exp = sym('(') * space() * elems - sym(')');
    exp.map(|vs| vs)
}
pub(super) fn sql_item_parse<'a>() -> Parser<'a, char, (String, String, bool, Vec<String>)> {
    let exp = comment().opt() * divi_type() + name() + is_preproc().opt() - char_string() - space()
        + col_names()
        - any().repeat(0..);
    exp.map(|(((dt, name), is_preproc), col_names)| {
        let is_preproc = is_preproc
            .unwrap_or("false".to_string())
            .parse()
            .unwrap_or(true);
        (dt, name, is_preproc, col_names)
    })
}
pub(super) fn sql_calc_item_parse<'a>(
) -> Parser<'a, char, (String, String, Vec<String>, Vec<String>)> {
    let exp = comment().opt() + divi_type() + ref_columns() - char_string() - space() + col_names()
        - any().repeat(0..);
    exp.map(|(((comment, dt), ref_columns), col_names)| {
        (comment.unwrap_or("".to_string()), dt, ref_columns, col_names)
    })
}
